/**
 * @file main_master.cpp Serverová časť programu pre paralelný výpočet diskrétneho logaritmu.
 *
 * @author Martin Kosorinský.
 * @date 30.10.2009.
 */

#include "CECContext.hpp"
#include "CRhoTabAlg.hpp"
#include "CPvmMasterCommands.hpp"
#include "CCmdLineParser.hpp"

using namespace std;

/**
 * @brief Definuje maximálny počet uzlov, ktorý je schopný program vytvoriť pomocou pvm_spawn().
 */
#define PVM_ECDLP_MAX_NODES   128
/**
 * @brief Definuje interval v sekundách, po ktorých bude ukladaný obsah tabuľky do súboru
 */
#ifndef ECDLP_AUTO_SAVE_INT
#define ECDLP_AUTO_SAVE_INT   600
#endif

/**
 * @brief Na základe vstupných parametrov -x, -y a -i nastaví bod, ktorého diskrétny logaritmus hľadáme.
 * Procedúra na základe vstupných parametrov inicializuje bod #CECContext::H. O súradniciach, ako aj index bodu sa
 * predpokladá, že sú zadané v desiatkovej sústave. V prípade nekompletného zadania súradníc a chýbajúceho
 * parametra -i je bod H inicializovaný náhodne.
 * @warning V prípade chybného formátu vstupu je program ukončený s chybovým hlásením knižnice miracl.
 * @param[in] cm Inicializovaný objekt obsahujúci vstupné parametre programu.
 */
void setPointH(CCmdLineParser & cm);
/**
 * @brief Na štandardný výstup (stdout) vypíše základné informácie o použití programu a jeho parametroch.
 */
void printUsage();
/**
 * @brief Funkcia počítajúca dolnú celú časť z dvojkového logaritmu vstupu.
 * @param n Kladné, nenulové celé číslo, ktorého logaritmus počítame.
 * @return Výstupom funkcie je dolná celá časť dvojkového logaritmu vstupu. V prípade nevhodného vstupu vráti -1
 */
int floorLog2(unsigned int n);
/**
 * @brief Vstupná funkcia serverovej časti programu pre výpočet ECDLP.
 * @param argc Počet vstupných argumentov programu.
 * @param argv Pole vstupných argumentov.
 */
int main(int argc, char* argv[]) {

    // int params = parseParams(argc, argv, &ecsPath, &digits, &filter, &N);
    CCmdLineParser cm(argc, argv, (char*)"he:d:f:n:x:y:i:l:s:");

    if (cm.isSet('h')) {
        printUsage();
        exit(EXIT_FAILURE);
    }

    if (!(cm.isSet('e') && cm.isSet('n'))) {
        cerr << "Error: '" << __FUNCTION__ << "': Chybaju povinne parametre programu (-e alebo -n)!" << endl;
        printUsage();
        exit(EXIT_FAILURE);
    }

    if (atoi(cm.getValue('n')) == 0 || (cm.isSet('d') && atoi(cm.getValue('d')) == 0) || (cm.isSet('f') && atoi(cm.getValue('f')) == 0)) {
        cerr << "Error: '" << __FUNCTION__ << "': Chybny format niektoreho zo [-n,-d,-f] vstupov!" << endl;
        printUsage();
        exit(EXIT_FAILURE);
    }

    //  spracovanie nepovinnych parametrov programu
    char * mirPrecis = (char*)"10";
    if (cm.isSet('d')) {
        mirPrecis = cm.getValue('d');
    }

    // inicializacia krivky a kniznice miracl -> pred jej volanim nesmie byt
    // ziadna alokacia premennych big alebo epoint!!!

    ifstream inputFile(cm.getValue('e'));
    CECContext::init(inputFile, MR_AFFINE, atoi(mirPrecis));
    setPointH(cm);
    inputFile.close();

    CRhoTable tabulka;
    CRhoTable::const_iterator it;

    if (cm.isSet('l')) {
        tabulka.load(cm.getValue('l'));
    }

    int myTID = pvm_mytid();

    if (myTID < 0) {
        cerr << "Error: 'main': Pravdepodobne nebezi pvmd!" << endl;
        exit(EXIT_FAILURE);
    }

    int tids[PVM_ECDLP_MAX_NODES];       // pole nodeov
    int ntids;       // pocet spustenych nodov
    int rtids;       // realny pocet beziacich uzlov

    big vysledok = mirvar(0);
    big a = mirvar(0);
    big b = mirvar(0);
    big A = mirvar(0);
    big B = mirvar(0);
    big p = mirvar(0);
    big ord = mirvar(0);
    epoint * P = epoint_init();
    epoint * G = epoint_init();
    epoint * H = epoint_init();

    CECContext::getParams(A, B, p, ord, G);
    CECContext::getPoint(H);

    char distFilter[20];
    strcpy(distFilter, "65536");
    if (cm.isSet('f')) {
        strcpy(distFilter, cm.getValue('f'));
        if (atoi(distFilter) < 1) {
            cerr << "Error: '" << __FUNCTION__ << "': Parameter filtrovacej funkcie musi byt kladne cele cislo (max=" << INT_MAX << ")!" << endl;
            exit(EXIT_FAILURE);
        }
    }
    else {
        // tu bude vypocet implicitnej hodnoty zalozenej na jednom teoretickom  dokumente
        int ordBits = (logb2(ord) / 2) - floorLog2((unsigned int)atoi(cm.getValue('n')));
        sprintf(distFilter, "%d", ordBits);
    }

    // odoslanie parametrov pre slave
    char * slaveArgs[] = {mirPrecis, distFilter, (char*)0 };
    ntids = rtids = pvm_spawn((char *)"ecdlp_slave", slaveArgs, PvmTaskDefault, (char*)"", atoi(cm.getValue('n')), tids);
    pvm_notify(PvmTaskExit, CPvmCommands::PVM_ECDLP_EXIT, ntids, tids); // cheme prijimat informaciu o ukonceni vypoctu uzlov

    tracer << "Parametre eliptickej krivky:" << endl;
    tracer << "a   = " << A << " (" << logb2(A) << " bits)\n";
    tracer << "b   = " << B << " (" << logb2(B) << " bits)\n";
    tracer << "p   = " << p << " (" << logb2(p) << " bits)\n";
    tracer << "ord = " << ord << " (" << logb2(ord) << " bits)" << endl;
    tracer << "G   = " << G << endl;
    tracer << "H   = " << H << endl;
    tracer << "------------------------------------------------------------------------" << endl;
    tracer << "Presnost (MIRACL): " << mirPrecis  << endl;
    tracer << "Parameter filtracie bodov: " << distFilter << endl;
    tracer << "Pocet vypoctovych uzlov: " << ntids << endl;
    tracer << "------------------------------------------------------------------------" << endl;

    if (ntids > 0) {
        for (int i = 0; i < ntids; i++) {
            CPvmMasterCommands::pvmSendECParams(tids[i], A, B, p, ord, G);
            CPvmMasterCommands::pvmSendTaskPoint(tids[i], H);
        }
        tracer << "------------------------------------------------------------------------" << endl;

        int pvmMsgTag;
        time_t startTime = time(0);
        time_t lastSaveTime = startTime;

        while (true) {

            pvmMsgTag = CPvmMasterCommands::pvmRecv(-1, -1);

            if (pvmMsgTag == CPvmCommands::PVM_ECDLP_DISTPOINT) {

                CPvmMasterCommands::pvmUnpackDistPoint(P, a, b);
                tracer << "Prijaty DP: " << P << "\ta=" << a << "\tb=" <<  b << endl;

                if ((it = tabulka.find(P)) != tabulka.end()) {
                    if (CRhoAlgCommon::computeIndex(a, b, it->second.first, it->second.second, vysledok)) {
                        if (!CRhoAlgCommon::checkIndex(vysledok)) {
                            cerr << "Error: '" << __FUNCTION__ << "': Vysledok je nespravny, neznama chyba!" << endl;
                            exit(EXIT_FAILURE);
                        }
                        break;
                    }
                    else {
                        tracer << "Vypocet indexu zlyhal, pokracujeme dalej v hladani." << endl;
                    }
                }
                else {
                    tabulka.add(P, a, b);
                    tracer << "Tabulka obsahuje: " << dec << tabulka.size() << " poloziek" << endl;
                    if (cm.isSet('s') && ((time(0) - lastSaveTime) > ECDLP_AUTO_SAVE_INT)) {
                        if (tabulka.save(cm.getValue('s'))) {
                            tracer << "Tabulka bola ulozena do suboru " << cm.getValue('s') << ", cas=" << time(0) << endl;
                        }
                        lastSaveTime = time(0);
                    }
                }
            }
            else if (pvmMsgTag == CPvmCommands::PVM_ECDLP_DL) {
                CPvmMasterCommands::pvmUnpackDL(vysledok);
                tracer << "Prijaty DL" << endl;
                break;
            }
            else if (pvmMsgTag == CPvmCommands::PVM_ECDLP_EXIT) {
                int crashTid;
                pvm_upkint(&crashTid, 1, 1);
                rtids--;
                cerr << "Error: '" << __FUNCTION__ << "': Vypoctovy uzol t" << hex << crashTid << dec << " predcasne skoncil (pravdepodobne padol)!" << endl;
                if (rtids <= 0) {
                    cerr << "Error: '" << __FUNCTION__ << "': Uz nepracuje ziaden vypoctovy uzol -> koncime!" << endl;
                    exit(EXIT_FAILURE);
                }
            }
        }
        tracer << "------------------------------------------------------------------------" << endl;
        for (int i = 0; i < ntids; i++) {
            tracer << "Kill signal pre uzol: t" << hex << tids[i] << dec << endl;
            pvm_kill(tids[i]);
        }
    }
    else {
        cerr << "Error: '" << __FUNCTION__ << "': Nepodarilo sa spustit vypoctove uzly!" << endl;
    }

    // overenie spravnosti najdeneho vysledku:
    if (!CRhoAlgCommon::checkIndex(vysledok)) {
        cerr << "Error: '" << __FUNCTION__ << "': Najdeny diskretny logaritmus je nespravny!" << endl;
        exit(EXIT_FAILURE);
    }

    tracer << "Vysledok je: ";
    cout << vysledok << endl;

    mirkill(vysledok);
    mirkill(a);
    mirkill(b);
    mirkill(A);
    mirkill(B);
    mirkill(p);
    mirkill(ord);

    epoint_free(G);
    epoint_free(P);
    epoint_free(H);

    pvm_exit();
    CECContext::free();

    return EXIT_SUCCESS;
}

void setPointH(CCmdLineParser & cm) {

    epoint * H = epoint_init();
    epoint * G = epoint_init();

    miracl *mip = get_mip();
    mip->IOBASE  = 10;

    CECContext::getGenerator(G);

    if (cm.isSet('i')) {    // neznamy bod bol dany exponentom vzhladom na generator G
        big i = mirvar(0);
        cinstr(i, cm.getValue('i'));
        ecurve_mult(i, G, H);
        CECContext::setPoint(H);
        mirkill(i);
    }
    else if (cm.isSet('x') && cm.isSet('y'))  {   // bod zadany affinymi suradnicami
        big x = mirvar(0);
        big y = mirvar(0);
        cinstr(x, cm.getValue('x'));
        cinstr(y, cm.getValue('y'));
        if (!epoint_set(x, y, 0, H)) {
            cerr << "Error: '" << __FUNCTION__ << "': Bod so vstupnymi suradnicami nelezi na krivke!" << endl;
            exit(EXIT_FAILURE);
        }
        CECContext::setPoint(H);
        mirkill(x);
        mirkill(y);
    }
    else if (cm.isSet('x') || cm.isSet('y')) {  // pripad zadania len jednej suradnice
        cerr << "Error: '" << __FUNCTION__ << "': Je potrebne zadat obe affinne suradnice bodu!" << endl;
        exit(EXIT_FAILURE);
    }
    else {  // pripad, ked neni zadany ani jeden z prepinacov -x, -y a -i
        CECContext::setPoint(); // budeme hladat nahodny bod
    }

    epoint_free(H);
    epoint_free(G);
}

void printUsage() {
    cout << "Serverova cast programu pre paralelny vypocet diskretneho logaritmu. Parametre oznacene (*) su povinne." << endl;
    cout << "Pouzitie: ecdlp_master [-h] -e <string> -d <int> -f <int> -n <int>" << endl;
    cout << "\t-h            :  Zobrazenie zakladnych informacii o programe" << endl;
    cout << "\t-e <string>   :  Cesta k ecs suboru s parametrami krivky (*)" << endl;
    cout << "\t-n <int>      :  Pocet uzlov, ktore budu vytvorene na vypocet (*)" << endl;
    cout << "\t-d <int>      :  Presnost kniznice miracl (number of digits)" << endl;
    cout << "\t-f <int>      :  Parameter filtrovacej funkcie (distinguished points), pocet trailing 0 v suradnici" << endl;
    cout << "\t-x <big int>  :  Prva afinna suradnica bodu H" << endl;
    cout << "\t-y <big int>  :  Druha afinna suradnica bodu H" << endl;
    cout << "\t-i <big int>  :  Exponent bodu vzhladom na zadany generator" << endl;
    cout << "\t-l <string>   :  Subor, z ktoreho budu pri spusteni nacitane data do tabulky" << endl;
    cout << "\t-s <string>   :  Subor, do ktoreho budu priebezne ukladane data  z tabulky" << endl;
}

int floorLog2(unsigned int n) {
    int pos = 0;
    if (n >= 1 << 16) { n >>= 16; pos += 16; }
    if (n >= 1 << 8) { n >>=  8; pos +=  8; }
    if (n >= 1 << 4) { n >>=  4; pos +=  4; }
    if (n >= 1 << 2) { n >>=  2; pos +=  2; }
    if (n >= 1 << 1) {           pos +=  1; }
    return ((n == 0) ? (-1) : pos);
}
